/**
 * 矩形
 */
interface IRect {
    x?: number;
    y?: number;
    width?: number;
    height?: number;
}

/**
 * 节点基础信息
 */
interface INodeInfo {
    width?: number;
    height?: number;
    anchorX?: number;
    anchorY?: number;
    scaleX?: number;
    scaleY?: number;
    scaleZ?: number;
}

/**
 * 截图工具
 * @author liwenin
 */
export default class CaptureUtils {

    /**
     * 全局摄像机
     */
    private static _camera: cc.Camera;
    /**
     * 画布
     */
    private static _canvas: HTMLCanvasElement;
    /**
     * 临时变量
     */
    //@ts-ignore
    private static _tmpMat4: cc.Mat4 = cc.mat4();
    /**
     * 临时变量
     */
    private static _tmpVec3: cc.Vec3 = new cc.Vec3();
    /**
     * 临时变量
     */
    private static _tmpInfo: INodeInfo = {};

    /**
     * 屏幕捕捉，核心功能——摄像机的锚点在中心点，设置捕捉区域的属性需要注意
     * @param area 捕捉区域，同时也是摄像机的父节点，以及渲染节点
     * @param rect 需要捕捉的内部区域
     */
    private static _capture(area: cc.Node, rect: IRect): cc.RenderTexture {
        var camera = CaptureUtils.getCamera(), cNode = camera.node, texture = new cc.RenderTexture;
        texture.initWithSize(rect.width, rect.height, cc['gfx'].RB_FMT_S8);
        cNode.setPosition(rect.x, rect.y);
        cNode.parent = area;
        //@ts-ignore
        camera.orthoSize = rect.height / 2;
        camera.targetTexture = texture;
        camera.render(area);
        camera.targetTexture = null;
        cNode.parent = null;
        return texture;
    }

    /**
     * 获取节点信息
     * @param node 
     */
    private static _getNodeInfo(node: cc.Node | cc.Scene): INodeInfo {
        var tmpInfo = CaptureUtils._tmpInfo, scale = node.getWorldMatrix(CaptureUtils._tmpMat4).getScale(CaptureUtils._tmpVec3);
        tmpInfo.scaleX = scale.x;
        tmpInfo.scaleY = scale.y;
        tmpInfo.scaleZ = scale.z;
        // 场景自身无宽高，故设置为屏幕宽
        if (node == cc.director.getScene()) {
            let view = cc.view['_visibleRect'] as cc.Rect;// 可以用cc.view.getVisibleSize()，不过每次调用都会新建一个Size，也可用cc.winSize
            tmpInfo.anchorX = tmpInfo.anchorY = 0;
            tmpInfo.width = view.width;
            tmpInfo.height = view.height;
        }
        else {
            tmpInfo.anchorX = node.anchorX;
            tmpInfo.anchorY = node.anchorY;
            tmpInfo.width = node.width;
            tmpInfo.height = node.height;
        }
        return tmpInfo;
    }

    /**
     * 获取摄像机
     */
    protected static getCamera(): cc.Camera {
        var camera = CaptureUtils._camera;
        if (!camera) {
            camera = CaptureUtils._camera = (new cc.Node('CaptureUtils')).addComponent(cc.Camera);
            // 采取自动适配尺寸，非全屏
            //@ts-ignore
            camera.alignWithScreen = false;
            //@ts-ignore
            camera.ortho = true;
            //@ts-ignore
            camera.nearClip = 0;/* 默认1，必须改为0否则黑屏 */
        }
        return camera;
    }

    /**
     * 获取画布——不支持document形式创建，则替换成对应平台提供的方式来创建即可
     */
    protected static getCanvas(): HTMLCanvasElement {
        return CaptureUtils._canvas || (CaptureUtils._canvas = document.createElement('canvas'));
    }

    /**
     * 捕捉节点的内部区域
     * @param area 需要捕捉的节点，默认当前场景下的Canvas；用Canvas的原因是它有宽高，而场景没有，这样rect也可不传
     * @param rect 需要捕捉的内部区域，坐标默认捕捉区域的中心点，宽高默认节点的宽高；注意若节点本身宽高为0，会导致捕捉异常，因此必须手动传入rect的宽高；
     */
    public static capture(area?: cc.Node, rect?: IRect): [cc.SpriteFrame, cc.RenderTexture] {
        var void0 = void 0, spf = new cc.SpriteFrame, info: INodeInfo;
        area === void0 && (area = cc.Canvas.instance.node || cc.director.getScene());
        rect === void0 && (rect = {});
        // 获取节点信息
        info = CaptureUtils._getNodeInfo(area);
        if (rect.width === void0) {
            rect.width = area.width * info.scaleX;
        }
        if (rect.height === void0) {
            rect.height = area.height * info.scaleY;
        }
        if (rect.x === void0) {
            rect.x = (.5 - area.anchorX) * area.width;
        }
        if (rect.y === void0) {
            rect.y = (.5 - area.anchorY) * area.height;
        }
        // 此处做了翻转，也可根据官方给的示例将像素点数据进行翻转（调用toImgurl来转换）
        let RenderTexture = CaptureUtils._capture(area, rect);
        spf.setTexture(RenderTexture);
        //@ts-ignore
        spf.setFlipY(true);
        return [spf, RenderTexture];
    }

    /**
     * 渲染纹理转图片路径，根据环境区分；
     * @param texture 渲染纹理
     * @description
     * 1、部分小游戏平台不支持toTempFilePathSync异步版（只有toTempFilePath），若需要也可则切换成Promise版本；
     * 2、返回的url可通过“cc.assetManager.loadRemote(url,{ext:'.png'}, function(e,r){r})”来获取纹理，也可通过"var img = new Image;img.src=url;var txt = new cc.Texture2D;txt.initWithElement(img)"来生成纹理（web）；
     */
    public static toImgUrl(texture: cc.RenderTexture): string {
        var width = texture.width, height = texture.height, url: string;
        if (cc.sys.isNative) {
            // 读取像素数据
            let data = texture.readPixels();

            // 获取纹理宽度和高度
            let width = texture.width;
            let height = texture.height;

            // 翻转像素数据的行顺序
            let flippedData = new Uint8Array(width * height * 4);
            for (let row = 0; row < height; ++row) {
                let startIdx = row * width * 4;
                let endIdx = (height - row - 1) * width * 4;
                flippedData.set(data.subarray(startIdx, startIdx + width * 4), endIdx);
            }

            // 保存翻转后的像素数据为图片
            let filePath = jsb.fileUtils.getWritablePath() + 'tmpImg.png';
            jsb.saveImageData(flippedData, width, height, filePath);

            url = filePath;
        }   else {
            // 通用模式，只要确保能创建一个2d canvas即可
            let canvas = CaptureUtils.getCanvas(), ctx = canvas.getContext('2d'), toTempFilePathSync = canvas['toTempFilePathSync'],
                data = texture.readPixels(), rowBytes = width * 4, row = 0;
            // 调整画布成当前纹理大小
            canvas.width = width;
            canvas.height = height;
            // 写入canvas
            while (row < height) {
                let srow = height - 1 - row, imageData = ctx.createImageData(width, 1), start = srow * width * 4;
                for (let i = 0; i < rowBytes; i++) {
                    imageData.data[i] = data[start + i];
                }
                ctx.putImageData(imageData, 0, row++);
            }
            if (typeof toTempFilePathSync === 'function') {
                // 异步版本
                // return new Promise(function (resolve) { toTempFilePath.call(canvas, { success(res) { ctx.clearRect(0, 0, width, height),resolve(res.tempFilePath) } }) });
                // 默认参数就是canvas自身大小、类型png，可以都不填写，一个空{}都可以，quality：图片质量，0~1，fileType='jpg'时有效
                url = toTempFilePathSync.call(canvas, { /* x: 0, y: 0, width: width, height: height, destWidth: width, destHeight: height, fileType: 'png', quality: 1 */ });
            }
            else {
                url = canvas.toDataURL('image/png');
            }
            // 用完立即清空数据
            ctx.clearRect(0, 0, width, height);
        }
        // 异步版本
        // return Promise.resolve(url);
        return url;
    }
}